package org.zebra.mybatis.plus.core.query;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.zebra.common.core.SortQuery;
import org.zebra.mybatis.plus.core.MPageQuery;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 查询助手
 *
 * @author zhanghongbin
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QueryHelper {

    /**
     * 根据查询条件构建 MyBatis Plus 查询条件封装对象
     *
     * @param query 查询条件
     * @param <Q>   查询条件数据类型
     * @param <R>   查询数据类型
     * @return MyBatis Plus 查询条件封装对象
     */
    public static <Q, R> QueryWrapper<R> build(Q query) {
        QueryWrapper<R> queryWrapper = new QueryWrapper<>();
        // 没有查询条件，直接返回
        if (null == query) {
            return queryWrapper;
        }
        if (query instanceof QueryWrapper) {
            return (QueryWrapper<R>) query;
        }
        // 获取查询条件中所有的字段
        Field[] fields;
        if (query instanceof MPageQuery) {
            fields = ReflectUtil.getFields(
                    query.getClass(),
                    field -> !field.getName().equals("size")
                            && !field.getName().equals("page")
                            && !field.getName().equals("sort")
                            && !field.getName().equals("order")
                            && !field.getName().equals("DEFAULT_PAGE_NUM")
                            && !field.getName().equals("DEFAULT_PAGE_SIZE"));
        } else {
            if (query instanceof SortQuery) {
                fields = ReflectUtil.getFields(
                        query.getClass(),
                        field -> !field.getName().equals("sort")
                                && !field.getName().equals("order"));
                buildSort((SortQuery) query, queryWrapper);
            } else {
                fields = ReflectUtil.getFields(query.getClass());
            }
        }
        List<Field> fieldList = Arrays.stream(fields)
                .filter(f -> !Modifier.isStatic(f.getModifiers()))
                .collect(Collectors.toList());
        fieldList.forEach(field -> buildQuery(query, field, queryWrapper));
        return queryWrapper;
    }

    private static void buildSort(SortQuery sortQuery, QueryWrapper queryWrapper) {
        String[] orderByArr = sortQuery.getSort().split(",");
        String[] isSortArr = sortQuery.getOrder().split(",");
        if (isSortArr.length != 1 && isSortArr.length != orderByArr.length) {
            return;
        }
        // 每个字段各自排序
        for (int i = 0; i < orderByArr.length; i++) {
            String orderByStr = orderByArr[i];
            String isSortStr = isSortArr.length == 1 ? isSortArr[0] : isSortArr[i];
            if ("asc".equals(isSortStr)) {
                queryWrapper.orderByAsc(orderByStr);
            } else if ("desc".equals(isSortStr)) {
                queryWrapper.orderByDesc(orderByStr);
            }
        }
    }

    /**
     * 构建 MyBatis Plus 查询条件封装对象
     *
     * @param query        查询条件
     * @param field        字段
     * @param queryWrapper MyBatis Plus 查询条件封装对象
     * @param <Q>          查询条件数据类型
     * @param <R>          查询数据类型
     */
    private static <Q, R> void buildQuery(Q query, Field field, QueryWrapper<R> queryWrapper) {
        boolean accessible = field.canAccess(query);
        try {
            field.setAccessible(true);
            // 如果字段值为空，直接返回
            Object fieldValue = field.get(query);
            if (ObjectUtil.isEmpty(fieldValue)) {
                return;
            }
            Query queryAnnotation = field.getAnnotation(Query.class);
            if (null == queryAnnotation) {
                String columnName =
                        CharSequenceUtil.toUnderlineCase(CharSequenceUtil.blankToDefault("", field.getName()));
                queryWrapper.eq(columnName, fieldValue);
                return;
            }
            // 解析查询条件
            parse(queryAnnotation, field.getName(), fieldValue, queryWrapper);
        } catch (Exception e) {
            throw new RuntimeException(
                    String.format(
                            "Build query occurred an error: {}. Query: {}, Field: {}.", e.getMessage(), query, field),
                    e);
        } finally {
            field.setAccessible(accessible);
        }
    }

    /**
     * 解析查询条件
     *
     * @param queryAnnotation 查询注解
     * @param fieldName       字段名
     * @param fieldValue      字段值
     * @param queryWrapper    MyBatis Plus 查询条件封装对象
     * @param <R>             查询数据类型
     */
    private static <R> void parse(
            Query queryAnnotation, String fieldName, Object fieldValue, QueryWrapper<R> queryWrapper) {
        // 解析多属性模糊查询
        // 如果设置了多属性模糊查询，分割属性进行条件拼接
        String[] blurryPropertyArr = queryAnnotation.blurry();
        if (ArrayUtil.isNotEmpty(blurryPropertyArr)) {
            queryWrapper.and(wrapper -> {
                for (String blurryProperty : blurryPropertyArr) {
                    wrapper.or().like(CharSequenceUtil.toUnderlineCase(blurryProperty), fieldValue);
                }
            });
            return;
        }

        // 解析单个属性查询
        // 如果没有单独指定属性名，就和使用该注解的属性的名称一致
        // 注意：数据库规范中列采用下划线连接法命名，程序规范中变量采用驼峰法命名
        String property = queryAnnotation.property();
        String columnName = CharSequenceUtil.toUnderlineCase(CharSequenceUtil.blankToDefault(property, fieldName));
        QueryType queryType = queryAnnotation.type();
        switch (queryType) {
            case EQUAL:
                queryWrapper.eq(columnName, fieldValue);
                break;
            case NOT_EQUAL:
                queryWrapper.ne(columnName, fieldValue);
                break;
            case GREATER_THAN:
                queryWrapper.gt(columnName, fieldValue);
                break;
            case LESS_THAN:
                queryWrapper.lt(columnName, fieldValue);
                break;
            case GREATER_THAN_OR_EQUAL:
                queryWrapper.ge(columnName, fieldValue);
                break;
            case LESS_THAN_OR_EQUAL:
                queryWrapper.le(columnName, fieldValue);
                break;
            case BETWEEN: {
                List<Object> between = new ArrayList<>((List<Object>) fieldValue);
                if (between.size() != 2) {
                    throw ExceptionUtil.wrapRuntime(CharSequenceUtil.format("[{}] 必须是一个范围", fieldName));
                }
                queryWrapper.between(columnName, between.get(0), between.get(1));
                break;
            }
            case LEFT_LIKE:
                queryWrapper.likeLeft(columnName, fieldValue);
                break;
            case INNER_LIKE:
                queryWrapper.like(columnName, fieldValue);
                break;
            case RIGHT_LIKE:
                queryWrapper.likeRight(columnName, fieldValue);
                break;
            case IN: {
                if (ObjectUtil.isEmpty(fieldValue)) {
                    throw ExceptionUtil.wrapRuntime(CharSequenceUtil.format("[{}] 不能为空", fieldName));
                }
                queryWrapper.in(columnName, (List<Object>) fieldValue);
                break;
            }
            case NOT_IN: {
                if (ObjectUtil.isEmpty(fieldValue)) {
                    throw ExceptionUtil.wrapRuntime(CharSequenceUtil.format("[{}] 不能为空", fieldName));
                }
                queryWrapper.notIn(columnName, (List<Object>) fieldValue);
                break;
            }
            case IS_NULL:
                queryWrapper.isNull(columnName);
                break;
            case IS_NOT_NULL:
                queryWrapper.isNotNull(columnName);
                break;
            default:
                throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType));
        }
    }
}
